/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package InternetHax;

import java.util.Vector;
import javax.microedition.lcdui.Image;

/**
 * The AppManager is responsable for the actual logic behind the apps
 * and as such contains all the code exicuted when an app is "used" or "equipped"
 * For creating apps from predefined blueprints, see {@link AppFactory}
 * @author LoginError
 */
public class AppManager {

    public static final int ROTATE_LEFT = 1;
    public static final int ROTATE_RIGHT = 2;

    DirectionMenu dirMenu; 

//Random random;
    public AppManager() {
        dirMenu = Gamestate.dirMenu;
    }

    /**
     * Adds tiles to a debug vector so they are drawn on the screen
     * @param tiles an array of tile locations relative to the location of an actor
     * @param tilePos the tile position of the actor using the App
     */
    private void addTilesToDebug(Point2d[] tiles, Point2d tilePos) {
        for (int i = 0; i < tiles.length; i++) {
            addTileToDebug(tiles[i], tilePos);
        }
    }

    private void addTileToDebug(Point2d tile, Point2d tilePos) {
            Point2d tempPoint = new Point2d(tile);
            if(tilePos != null)
                tempPoint.add(tilePos);
            Gamestate.debugTiles.addElement(tempPoint);
    }

    /**
     * Adds tiles to a debug vector so they are drawn on the screen
     * @param tiles a vector of tile positions relative to the location of an actor
     * @param tilePos the tile position of the actor using the app
     */
    private void addTilesToDebug(Vector tiles, Point2d tilePos) {
        for (int i = 0; i < tiles.size(); i++) {
            addTileToDebug((Point2d)tiles.elementAt(i), tilePos);
        }
    }

    /**
     * Logic to be exicuted when a monster uses an app
     * @param user the monster using the app
     * @param app the app to be used
     * @return returns true if turn is over, false if not
     */

    public int activeApp(Actor user, ActiveApp app,int index)
    {
        
        Vector targets = new Vector();
        int itemID = app.getAppID();
        Point2d tilePos = user.getTilePosition();
        Point2d[] damageTiles;
        int damage;
        
        switch (itemID) {
            case Constants.APP_SIMPLEPUNCH:
                targets = getActorsAdjacent(tilePos.x, tilePos.y);
                
                if (targets.isEmpty()) {
                    break;
                }
                Actor target = (Actor) targets.elementAt(0);
                

                damage = roll(2, 5);
                target.alterHP(-damage);
                Gamestate.console.print(user.getName() + " punches  " + target.getName()+ " for " + damage + " damage. ");
                user.loseAP(3);
                break;

            case Constants.APP_DEBUGSHOTGUN:
                 {
                    int shotgunDamage = 0;
                    
                    damageTiles = new Point2d[4];
                    
                    damageTiles[0] = new Point2d(0, -1);
                    damageTiles[1] = new Point2d(0, -2);
                    damageTiles[2] = new Point2d(1, -2);
                    damageTiles[3] = new Point2d(-1, -2);
                    int returnedDirection = Constants.DIRECTION_NONE;
                    
                    if(user.isHero())
                        returnedDirection = Gamestate.dirMenu.getDirection(false, false);
                    else
                        returnedDirection = choseBestDirection(damageTiles, true, user);

                    
                    if(returnedDirection != Constants.DIRECTION_NONE){
                        user.setFacing(returnedDirection);
                        pointPatternAtDirection(damageTiles, returnedDirection);
                        
                        addTilesToDebug(damageTiles, tilePos);

                         //if someone's in the first tile they get hit for 2x damage
                         Actor foundActor = Gamestate.theMap.getActorOnTile(tilePos.x + damageTiles[0].x, tilePos.y + damageTiles[0].y);
                             if (foundActor != null) {
                             shotgunDamage = roll(2, 10);
                             Gamestate.console.print(user.getName() +" blasts "+ foundActor.getName() +" point blank for " + shotgunDamage + " damage. ");
                             foundActor.alterHP(-1 * shotgunDamage);
                             break;
                         }

                         for (int i = 1; i < 4; i++) {
                             foundActor = Gamestate.theMap.getActorOnTile(tilePos.x + damageTiles[i].x, tilePos.y + damageTiles[i].y);

                             if (foundActor != null) {
                                 shotgunDamage = roll(1, 10);
                                 Gamestate.console.print(user.getName() +" blasts "+ foundActor.getName() +" for " + shotgunDamage + " damage." );
                                 foundActor.alterHP(-1 * shotgunDamage);
                             }
                         }
                    
                    user.loseAP(3);
                    }
                }
            break;

            case Constants.APP_LASERGUN:
                damageTiles = new Point2d[5];
                for (int i = 0; i < damageTiles.length; i++) {
                    damageTiles[i] = new Point2d(0,-i-1);
                }

                int returnedDirection = Constants.DIRECTION_NONE;

                if(user.isHero())
                    returnedDirection = Gamestate.dirMenu.getDirection(false, false);
                else
                    returnedDirection = choseBestDirection(damageTiles, true, user);

                user.setFacing(returnedDirection);
                pointPatternAtDirection(damageTiles, returnedDirection);
                        
                if(returnedDirection != Constants.DIRECTION_NONE)
                {
                    addTilesToDebug(damageTiles, tilePos);
                    for (int i = 0; i < damageTiles.length; i++) {
                        Actor foundActor = Gamestate.theMap.getActorOnTile(tilePos.x + damageTiles[i].x, tilePos.y + damageTiles[i].y);
                        if(foundActor != null)
                        {
                            damage = roll(2,5);
                            Gamestate.console.print(user.getName() +" shoots "+ foundActor.getName() +" with a laser for " + damage + " damage. " );
                            foundActor.alterHP(-1 * damage);
                            break;
                        }

                    }
                }
                user.loseAP(3);
            break;


            case Constants.APP_SELFDESTRUCT:
            {
                Vector explodeTiles = getTilesInSquare( tilePos.x -1, tilePos.y -1, 3, 3);
                //addTilesToDebug(explodeTiles, null);

                if(user.isMonster() && weighPatternAbsolute(explodeTiles,user,true,false) <= 0)
                    break; //monsters will check to see if it's worth it before exploding, obstacles won't

                Gamestate.console.print(user.getName() + " explodes! ");
                
                for (int i = 0; i < explodeTiles.size(); i++) {
                    Point2d theTile = (Point2d)explodeTiles.elementAt(i);
                    Actor foundActor = (Actor)Gamestate.theMap.getActorOnTile(theTile.x, theTile.y);
                    if(foundActor != null && foundActor != user){
                        Gamestate.console.print(foundActor.getName() + " takes massive damage. ");
                        foundActor.alterHP(-50);
                    }
                }
                if(user.isHero())
                    Gamestate.console.print("You're doing it wrong! ");
                user.die();
            }
            break;

            default:
            break;
        }
        return -1;
    }


    public boolean useApp(Actor user, App app, int index) {
        boolean turnOver = false;

        int apptype = app.getType();
        switch(apptype)
        {      
            case Constants.APPTYPE_ACTIVE:
                activeApp(user,(ActiveApp)app,index);
            break;

            case Constants.APPTYPE_CONSUMABLE:
                consumableApp(user,(ConsumableApp)app,index);
            break;

            case Constants.APPTYPE_LOGIC:
                logicApp(user,(LogicApp)app);
            break;
        }

        return turnOver;

    }

    private void consumableApp(Actor user, ConsumableApp app, int index) {
        switch(app.appID)
        {
            case Constants.APP_HEALPACK:
            {
                if(user.isMonster() && (user.HP * 100/user.MaxHP) > 50)
                    break;

                user.alterHP(20);
                Gamestate.console.print(user.getName() + " recovers 20 hp. ");
                app.uses--;
            }
            break;
        }
        if(app.uses <= 0)
        {
            if(user.isHero())
            {
                Hero theHero = (Hero)user;
                App[] equippedApps = theHero.getEquippedApps();

                if(index == -1)
                {
                    for (int i = 0; i < equippedApps.length; i++) {
                        if(app.equals(equippedApps[i]))
                        {
                            equippedApps[i] = null;
                        }
                    }
                }
                else
                    theHero.getInventory().removeElementAt(index);
                
                Gamestate.console.print("The app is used up");//TODO: put app name here
            }
            else if(user.isMonster())
            {
                Monster aMonster = (Monster)user;
                aMonster.getInventory().removeElementAt(index);
            }
            
        }
    }

    private void logicApp(Actor user, LogicApp logicApp) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Uses a random number generator to "roll" dice and returns the result
     * @param dice the number of dice to roll
     * @param maxNum the number of sides on each die
     * @return the sum of all dice rolls
     */
    private int roll(int dice, int maxNum) {
        int total = 0;

        for (int i = 0; i < dice; i++) {
            total += Gamestate.generator.nextInt(maxNum) + 1;
        }

        return total;
    }    

    private boolean validateAttack(Point2d[] aimTiles, Actor user,boolean ignoreSelf, boolean friendlyFire)
    {
        int totalWeight = 0;
        
        for (int i = 0; i < aimTiles.length; i++) {
            totalWeight += weighTile(user,aimTiles[i].x,aimTiles[i].y,ignoreSelf, friendlyFire);
        }

        Toolbox.printDebug(user.name + ((totalWeight > 0) ? ": Good idea" : ":Not worth it"),2);
        
        return totalWeight > 0;
    }
    
    
    private int weighPatternAbsolute(Vector aimTiles, Actor user,boolean ignoreSelf, boolean friendlyFire)
    {
        int totalWeight = 0;
        
        for (int i = 0; i < aimTiles.size(); i++) {
            Point2d point = (Point2d)aimTiles.elementAt(i);
            totalWeight += weighTile(user,point.x,point.y, ignoreSelf, friendlyFire);
        }

        Toolbox.printDebug(user.name + ((totalWeight > 0) ? ": Good idea" : ":Not worth it"),2);

        return totalWeight;
    }
    
    private int weighPatternRelative(Vector aimTiles, Actor user,boolean ignoreSelf, boolean friendlyFire)
    {
        int totalWeight = 0;
        
        for (int i = 0; i < aimTiles.size(); i++) {
            Point2d point = (Point2d)aimTiles.elementAt(i);
                    point.add(user.tilePosition);
            totalWeight += weighTile(user,point.x,point.y, ignoreSelf, friendlyFire);
        }
        
        return totalWeight;
    }


    private int weighTile(Actor user, int x, int y, boolean ignoreSelf, boolean friendlyFire)
    {
        int actorWeight = 0;
        Actor foundActor = Gamestate.theMap.getActorOnTile(x,y);

                if(foundActor != null)
                {
                    if(ignoreSelf && foundActor == user)
                        return 0;
                    
                    actorWeight =(int)( 100 /(((double)foundActor.getHP() / (double)foundActor.getMaxHP() * 100))); //generate a weight based on the remeaining HP, weight is larger the lower the HP is
                    
                    if(foundActor.isMonster() && !friendlyFire)
                        actorWeight *= -1; //TODO: change this to filter based on "team"

                    Toolbox.printDebug("Found " + foundActor.name+ " weighted as "+actorWeight, 2);
                }
                
        return actorWeight;
    }



    private int choseBestDirection(Point2d[] aimTiles, boolean rotate, Actor user){
        int bestChoice = -1;
        int currentWeight = 0;
        int bestWeight = 0;
        
        for(int i = Constants.DIRECTION_UP; i <= Constants.DIRECTION_RIGHT;i++)
        {
            for(int j = 0 ; j < aimTiles.length; j++)
            {
                currentWeight += weighTile(user, aimTiles[j].x, aimTiles[j].y,true,false);
            }

            if(currentWeight > bestWeight)
            {
                bestChoice = i;
            }
            
            currentWeight = 0;
            rotatePattern(aimTiles, ROTATE_LEFT);    
        }
        
        return bestChoice;
    }
    
    
    
    /**
     * Returns all actors found within a specified square as a Vector
     * @param ignoreMe this actor is not included in the returned actors, use this to ignore the user of an app from a square in which it resides
     * @param x x location of the top left of the square
     * @param y y location of the top left of the square
     * @param height height of square in tiles
     * @param width width of square in tiles
     * @return a Vector containing all actors found with that square
     */
    private Vector getActorsInSquare(Actor ignoreMe, int x, int y, int height, int width) {
        Vector foundActors = new Vector();
        Map theMap = Gamestate.theMap;

        for (int i = x; i < (x + width); i++) {
            for (int j = y; (j < y + width); j++) {
                
                Actor theActor = theMap.getActorOnTile(i, j);
                if (theActor != null && theActor != ignoreMe)//TODO change this to filter better
                {
                    foundActors.addElement(theActor);

                }
            }
        }
        return foundActors;
    }


    private Vector getTilesInSquare( int x, int y, int height, int width) {
        Vector foundTiles = new Vector();
        Map theMap = Gamestate.theMap;

        for (int i = x; i < (x + width); i++) {
            for (int j = y; (j < y + width); j++) {
                foundTiles.addElement(new Point2d(i,j));
            }
        }
        return foundTiles;
    }

    /**
     * Returns all actors found either above, below, to the right or left of the given square
     * @param x x location of the tile
     * @param y y location of the tile
     * @return a Vector containging all actors found
     */
    private Vector getActorsAdjacent(int x, int y) {
        int i, j;

        i = x - 1;
        j = y;

        Map theMap = Gamestate.theMap;

        Vector foundActors = new Vector();

        Actor theActor = theMap.getActorOnTile(i, j);
        if (theActor != null )//TODO change this to filter better
        {
            foundActors.addElement(theActor);
        }

        i = x;
        j = y - 1;


        theActor = theMap.getActorOnTile(i, j);
        if (theActor != null )//TODO change this to filter better
        {
            foundActors.addElement(theActor);
        }
        i = x;
        j = y + 1;



        theActor = theMap.getActorOnTile(i, j);
        if (theActor != null)//TODO change this to filter better
        {
            foundActors.addElement(theActor);
        }
        i = x + 1;
        j = y;


        theActor = theMap.getActorOnTile(i, j);
        if (theActor != null )//TODO change this to filter better
        {
            foundActors.addElement(theActor);
        }

        return foundActors;
    }


    /**
     * rotates a list of 2d points /around 0,0 this allows weapons to more easily be aimed in the 4 directions (UDLR)
     * @param points the list of points to rotate
     * @param direction the direction to rotate in
     * @return the list of rotated points
     */
    private Point2d[] rotatePattern(Point2d[] points, int direction) {

        for (int i = 0; i < points.length; i++) {
            if (direction == ROTATE_LEFT) {
                int temp = points[i].x;
                points[i].x = points[i].y;
                points[i].y = -1 * temp;
            }
            else if (direction == ROTATE_RIGHT) {
                int temp = points[i].x;
                points[i].x = -1 * points[i].y;
                points[i].y = temp;
            }

        }

        return points;
    }

    private Point2d[] pointPatternAtDirection(Point2d[] points, int direction) {

        
        if (direction == Constants.DIRECTION_UP)
            return points;
            
        else if (direction == Constants.DIRECTION_LEFT)
            return rotatePattern(points, ROTATE_LEFT);
            
        else if (direction == Constants.DIRECTION_RIGHT)
            return rotatePattern(points, ROTATE_RIGHT);
            
        else if (direction == Constants.DIRECTION_DOWN)
        for(int i = 0; i < points.length; i++)
        {
            points[i].x *= -1;
            points[i].y *= -1;
        }

        return points;
    }
    
    
    
    /**
     * Draws a vector across the map and returns any actors on tiles it crosses
     * uses Bresenham's Line algorithm ported from C++ 
     * {@linkplain  http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm}
     * //TODO: have this return an array of point2d 
     * @param startx 
     * @param starty
     * @param endx
     * @param endy
     * @return
     */
    private Vector getActorsInLine(int startx, int starty, int endx, int endy) {


        int Dx = endx - startx;
        int Dy = endy - starty;
        int steep = ((Math.abs(Dy) >= Math.abs(Dx)) ? 1 : 0);

        if (steep == 1) {
            //SWAP(startx, starty);
            int temp = startx;
            startx = starty;
            starty = temp;


            //SWAP(endx, endy);
            temp = endx;
            endx = endy;
            endy = temp;



            // recompute Dx, Dy after swap
            Dx = endx - startx;
            Dy = endy - starty;
        }
        int xstep = 1;
        if (Dx < 0) {
            xstep = -1;
            Dx = -Dx;
        }
        int ystep = 1;
        if (Dy < 0) {
            ystep = -1;
            Dy = -Dy;
        }
        int TwoDy = 2 * Dy;
        int TwoDyTwoDx = TwoDy - 2 * Dx; // 2*Dy - 2*Dx
        int E = TwoDy - Dx; //2*Dy - Dx
        int y = starty;
        int xDraw, yDraw;
        for (int x = startx; x != endx; x += xstep) {
            if (steep == 1) {
                xDraw = y;
                yDraw = x;
            }
            else {
                xDraw = x;
                yDraw = y;
            }
            // plot
            //plot(xDraw, yDraw);
            Gamestate.console.print("xDraw = " + xDraw + " yDraw = " + yDraw);
            // next
            if (E > 0) {
                E += TwoDyTwoDx; //E += 2*Dy - 2*Dx;
                y = y + ystep;
            }
            else {
                E += TwoDy; //E += 2*Dy;
            }
        }

        return null;
    }

    /*public int playerUse(Actor user, int itemID)
    {
    throw new UnsupportedOperationException("not implemented");
    }*/
    public String getDiscription(int id) {
        return "A glitch in the system has created garbage items that do absolutly nothing, you happen to be the proud owner of one.";
    }

    public String getName(int id) {
        return "Error";
    }
}
